package com.google.gwt.command.client;

import java.util.Stack;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.GwtEvent;

/**
 * The invoker is in charge of the Undo / Redo, logging and dispatching
 * management.
 * 
 * @author romain biard
 * 
 */
public class Invoker {

	public enum WHEN {
		BEFORE, AFTER, UNDO, REDO
	}

	private InvokerManager manager = GWT.create(InvokerManager.class);

	private Stack<GwtEvent<?>> undoCache = new Stack<GwtEvent<?>>();

	private Stack<GwtEvent<?>> redoCache = new Stack<GwtEvent<?>>();

	private EventBus eventBus;

	public Invoker(EventBus eventBus) {
		this.eventBus = eventBus;
		manager.bindCommands(eventBus, this);
		registerUndo();
		registerNeverComeBack();
	}

	private void registerNeverComeBack() {
		getEventBus().addHandler(NeverComeBackEvent.getType(), new NeverComeBackEvent.NeverComeBackHandler() {

			public void onNeverComeBack(NeverComeBackEvent event) {
				clearUnDoCache();
			}
		});
	}

	private void registerUndo() {
		getEventBus().addHandler(UndoEvent.getType(), new UndoEvent.UndoHandler() {

			public void onUnDo(UndoEvent event) {
				undo();
			}
		});
	}

	protected void undo() {
		GwtEvent<?> event = undoCache.pop();
		if (event != null) {
			logEvent(event, WHEN.UNDO);
			dispatch(event, true);
			redoCache.push(event);
		}
	}

	protected void redo() {
		GwtEvent<?> event = redoCache.pop();
		if (event != null) {
			logEvent(event, WHEN.REDO);
			dispatch(event, false);
			undoCache.push(event);
		}
	}

	protected void run(GwtEvent<?> event, boolean isUnDo) {
		saveReceivedEvent(event);
		clearReDoCache();
		dispatch(event, isUnDo);
	}

	private void clearReDoCache() {
		redoCache.clear();
	}

	private void clearUnDoCache() {
		undoCache.clear();
	}

	private void dispatch(GwtEvent<?> event, boolean isUnDo) {
		beforeDispatch(event);
		manager.dispatchEvent(event, isUnDo);
		afterDispatch(event);
	}

	private void saveReceivedEvent(GwtEvent<?> event) {
		undoCache.push(event);
	}

	private void afterDispatch(GwtEvent<?> event) {
		logEvent(event, WHEN.AFTER);
	}

	protected void logEvent(GwtEvent<?> event, WHEN state) {
		switch (state) {
		case BEFORE:
			GWT.log("Event caught for processing: " + event.toDebugString());
			break;
		case AFTER:
			GWT.log("Event sent for processing: " + event.toDebugString());
			break;
		case UNDO:
			GWT.log("Undo requested for event : " + event.toDebugString());
			break;
		case REDO:
			GWT.log("Redo requested for event : " + event.toDebugString());
			break;
		}

	}

	private void beforeDispatch(GwtEvent<?> event) {
		logEvent(event, WHEN.BEFORE);
	}

	public EventBus getEventBus() {
		return eventBus;
	}
}
